#include "creature.h"
#include "map.h"
#include "item.h"
#include "game.h"
#include "random.h"
#include "player.h"

Creature::Creature( int sStr, int sDex, int sInt, int randomlyDistributedPoints )
: _sStr( sStr )
, _sDex( sDex )
, _sInt( sInt )
, _hp( 0 )
, _mp( 0 )
, _hpRegen( 0 )
, _mpRegen( 0 )
{
    for ( uint i = 0; i < uint( std::max( 0, randomlyDistributedPoints ) ); i++ ) {
        int r = Random::Int( 3 );
        if ( r == 0 )
            ++_sStr;
        if ( r == 1 )
            ++_sDex;
        if ( r == 2 )
            ++_sInt;
    }
    _hp = 100;
    _mp = 100;
}

void Creature::Destroy()
{
    ItemList t;
    GetItemsInInventory( t );
    foreach ( Item* i, t )
        i->Destroy();
    Actor::Destroy();
}

double Creature::CanSeeTile( int x, int y, bool calculateLightValue ) const
{
    const Map* map = GetMap();
    if ( !map )
        throw std::runtime_error( "Checking tile sight on invalid map" );

    int sightRange = 0;
    bool seeInDark = false;
    bool ignore    = false;
    Call< BISight >( &sightRange, &seeInDark, &ignore );

    uint d = map->GetDistance( GetX(), GetY(), x, y );
    bool   los   = sightRange > 0 && map->IsInLOS( GetX(), GetY(), x, y );
    double light = ( seeInDark || /*!d ||*/ GetMap()->IsOutsideMap() ) ? 1.0 : 
        ( calculateLightValue ? map->GetLightValue( x, y ) : map->GetCalculatedLightValue( x, y ) );
    return ( /*!d ||*/ ( los && int( d ) <= sightRange ) ) ? light : 0.0;
}

bool Creature::CanSeeActor( const Actor* a ) const
{
    int ignore        = 0;
    bool bignore      = false;
    bool seeInvisible = false;
    bool invisible    = false;

    Call< BISight >( &ignore, &bignore, &seeInvisible );
    a->Call< BIInvisible >( &invisible );
    return seeInvisible || !invisible;
}

void Creature::GetViewRectangle( int& left, int& right, int& top, int& bottom ) const
{
    // Get the viewport quadrant dimensions
    int rw = Game::WIDTH / 2;
    int lw = rw + Game::WIDTH % 2;
    int th = Game::HEIGHT / 2;
    int bh = th + Game::HEIGHT % 2;

    const Map* map = GetMap();
    if ( !map )
        throw std::runtime_error( "Getting view on invalid map" );

    // Find the rectangle of the map to draw
    if ( map->GetWidth() >= int( Game::WIDTH ) ) {
        left   = GetX() - lw;
        right  = GetX() + rw;
    }
    else {
        left   = int( map->GetWidth() ) / 2 - lw;
        right  = int( map->GetWidth() ) / 2 + rw;
    }
    if ( map->GetHeight() >= int( Game::HEIGHT ) ) {
        top    = GetY() - th;
        bottom = GetY() + bh;
    }
    else {
        top    = int( map->GetHeight() ) / 2 - th;
        bottom = int( map->GetHeight() ) / 2 + bh;
    }

    // If we're viewing outside the map bounds, try to shift it over
    int shift = std::min( -left, int( map->GetWidth() ) - right );
    if ( shift > 0 ) {
        left  += shift;
        right += shift;
    }
    shift = std::min( -top, int( map->GetHeight() ) - bottom );
    if ( shift > 0 ) {
        top    += shift;
        bottom += shift;
    }
    shift = std::min( right - int( map->GetWidth() ), left );
    if ( shift > 0 ) {
        left  -= shift;
        right -= shift;
    }
    shift = std::min( bottom - int( map->GetHeight() ), top );
    if ( shift > 0 ) {
        top    -= shift;
        bottom -= shift;
    }
}

bool Creature::MapToViewCoordinates( int& x, int& y ) const
{
    int left, right, top, bottom;
    GetViewRectangle( left, right, top, bottom );
    x = x - left;
    y = y - top;
    return x >= 0 && y >= 0 && x < int( Game::WIDTH ) && y < int( Game::HEIGHT );
}

void Creature::RenderMap()
{
    Map* map = GetMap();

    GetMap()->GetSyncIO()._io.Clear( 0 );
    int left, right, top, bottom;
    GetViewRectangle( left, right, top, bottom );
    map->CalculateLightValues( left, top, right, bottom );

    for ( int x = left; x < right; x++ ) {
        for ( int y = top; y < bottom; y++ ) {

            if ( x < 0 || x >= int( map->GetWidth() ) || y < 0 || y >= int( map->GetHeight() ) )
                continue;

            int i = x - left;
            int j = y - top;

            double light = CanSeeTile( x, y, false );
            double hudLight = light < 0.5 ? std::pow( light * 2.0, 0.33 ) : 1.0;

            Tile& t = map->GetTile( x, y );
            if ( light > 0.0 || map->GetDistance( GetX(), GetY(), x, y ) <= 1 )
                t.Memorise( *map, x, y );

            bool drawn = false;
            const IO::SymList& tileSym = t.GetSymbol();

            ActorList list;
            t.GetActorsOnTile( list );

            foreach ( const Actor* a, list ) {
                IO::SymList list;
                a->Call< BISymbol >( &list );
                if ( !list.empty() && ( ( light > 0.0 && CanSeeActor( a ) ) || a == this ) ) {
                    for ( uint k = 0; k < list.size(); ++k ) {
                        Colour& c = list[ k ].second;
                        c.r = uchar( double( c.r ) / ( 1.0 + 5.0 * ( 1.0 - hudLight ) ) );
                        c.g = uchar( double( c.g ) / ( 1.0 + 5.0 * ( 1.0 - hudLight ) ) );
                        c.b = uchar( double( c.b ) / ( 1.0 + 3.5 * ( 1.0 - hudLight ) ) );
                    }
                    GetMap()->GetSyncIO()._io.Draw( list, i, j, 0 );
                    drawn = true;
                    break;
                }
            }

            if ( !drawn ) {
                IO::SymList list = tileSym;
                for ( uint k = 0; k < list.size(); ++k ) {
                    Colour& c = list[ k ].second;
                    c.r = uchar( double( c.r ) / ( 1.0 + 5.0 * ( 1.0 - hudLight ) ) );
                    c.g = uchar( double( c.g ) / ( 1.0 + 5.0 * ( 1.0 - hudLight ) ) );
                    c.b = uchar( double( c.b ) / ( 1.0 + 3.5 * ( 1.0 - hudLight ) ) );
                }
                GetMap()->GetSyncIO()._io.Draw( list, i, j, 0 );
            }

        }
    }

}

void Creature::AddItem( Item* item )
{
    if ( IsDestroyed() )
        throw std::runtime_error( "Item added to destroyed actor" );

    if ( item == 0 )
        throw std::runtime_error( "Null item added to actor" );

    if ( IsItemInInventory( item ) )
        throw std::runtime_error( "Duplicate item added to actor" );

    _items.insert( item );
}

void Creature::RemoveItem( Item* item )
{
    if ( IsDestroyed() )
        throw std::runtime_error( "Item removed from destroyed actor" );

    if ( item == 0 )
        throw std::runtime_error( "Null item removed from actor" );

    if ( !IsItemInInventory( item ) )
        throw std::runtime_error( "Item removed from unoccupied actor" );

    _items.erase( item );
}

bool Creature::IsItemInInventory( const Item* item ) const
{
    return _items.count( const_cast< Item* >( item ) ) != 0;
}

void Creature::GetItemsInInventory( ItemList& outputList, uint typeMask, uint flammableTypeMask )
{
    foreach ( Item* i, _items ) {
        uint type, flammableType;
        i->Call< BIItemType >( &type );
        i->Call< BIFlammable >( &flammableType );
        if ( ( !typeMask || type & typeMask ) && ( !flammableTypeMask || flammableType & flammableTypeMask ) && !i->IsDestroyed() )
            outputList.push_back( i );
    }
    std::sort( outputList.begin(), outputList.end(), &Item::PrioritySort );
}

void Creature::GetItemsInInventory( ConstItemList& outputList, uint typeMask, uint flammableTypeMask ) const
{
    foreach ( const Item* i, _items ) {
        uint type, flammableType;
        i->Call< BIItemType >( &type );
        i->Call< BIFlammable >( &flammableType );
        if ( ( !typeMask || type & typeMask ) && ( !flammableTypeMask || flammableType & flammableTypeMask ) && !i->IsDestroyed() )
            outputList.push_back( i );
    }
    std::sort( outputList.begin(), outputList.end(), &Item::PrioritySort );
}

int Creature::CountInventoryWeight() const
{
    int r = 0;
    foreach ( Item* i, _items ) {
        if ( i->IsDestroyed() )
            continue;
        uint w;
        i->Call< BIItemWeight >( &w );
        r += int( w );
    }

    return r;
}

void Creature::GetWornItems( uint type, ItemList& outputList )
{
    foreach ( Item* i, _items ) {
        uint t;
        i->Call< BIItemType >( &t );
        if ( i->IsWorn() && !i->IsDestroyed() && ( t & type || !type ) )
            outputList.push_back( i );
    }
}

void Creature::GetWieldedItems( uint type, ItemList& outputList )
{
    foreach ( Item* i, _items ) {
        uint t;
        i->Call< BIItemType >( &t );
        if ( i->IsWielded() && !i->IsDestroyed() && ( t & type || !type ) )
            outputList.push_back( i );
    }
}

bool Creature::Priority( int* priority ) const
{
    *priority += 1000;
    return true;
}

bool Creature::Blocking( bool* blocking, bool* losBlocking, uint* pushType ) const
{
    *blocking = true;
    *losBlocking = false;
    *pushType = PUSH_DISPLACE;
    return true;
}

bool Creature::Stats( int* hpMax, int* mpMax, int* sStr, int* sDex, int* sInt,
                    int* ac, int* magicRes, int* fireRes ) const
{
    *hpMax += 100;
    *mpMax += 100;
    *sStr += _sStr;
    *sDex += _sDex;
    *sInt += _sInt;
    return true;
}

bool Creature::DerivedStats( double* hpRegen, double* mpRegen, int* weightMax ) const
{
    int hpMax, mpMax, sStr, sDex, sInt, ac, magicRes, fireRes;
    Call< BIStats >( &hpMax, &mpMax, &sStr, &sDex, &sInt, &ac, &magicRes, &fireRes );
    *hpRegen += sStr * 0.0002;
    *mpRegen += sInt * 0.0002;
    *weightMax += 200 + 40 * sStr;
    return true;
}

bool Creature::Inventory( ItemList* list )
{
    GetItemsInInventory( *list );
    return true;
}

bool Creature::OnPush( Actor* target, bool reverse, bool* pushed )
{
    target->Call< BIOnPushed >( this, reverse, pushed, true );
    return true;
}

bool Creature::OnPickup( Item** item, bool* pickedUp )
{
    Item* it = *item;
    int weightMax;
    double ignore;
    Call< BIDerivedStats >( &ignore, &ignore, &weightMax );
    if ( CountInventoryWeight() >= weightMax ) {
        Bank().PlayerUI( PLAYERUI_PICKUP_FAIL, this );
        return false;
    }
    it->SetActor( this );
    *pickedUp = true;
    return true;
}

bool Creature::OnDrop( Item** item, bool* dropped )
{
    Item* it = *item;
    if ( !IsItemInInventory( it ) )
        return false;
    if ( it->IsWielded() ) {
        bool removed = false;
        Call< BIOnRemove >( &it, &removed );
        if ( !removed ) {
            Bank().PlayerUI( PLAYERUI_FAIL_WIELDING, this );
            return false;
        }
    }
    if ( it->IsWorn() ) {
        bool removed = false;
        Call< BIOnRemove >( &it, &removed );
        if ( !removed ) {
            Bank().PlayerUI( PLAYERUI_FAIL_WEARING, this );
            return false;
        }
    }
    it->SetMap( GetMap(), GetX(), GetY() );
    Actor* actor;
    if ( it->Call< BIIsActor >( &actor ) && actor != 0 )
        Bank().Binary( BINARY_ITEM_DROP, this, actor );
    *dropped = true;
    return true;
}

bool Creature::OnWear( Item** item, bool* worn )
{
    Item* it = *item;
    uint type;
    it->Call< BIItemType >( &type );
    uint t = type & BIItemType::WEARABLE;
    if ( !IsItemInInventory( it ) )
        return false;
    if ( !t ) {
        Bank().PlayerUI( PLAYERUI_FAIL_UNWEARABLE, this );
        return false;
    }
    if ( it->IsWorn() ) {
        Bank().PlayerUI( PLAYERUI_FAIL_ALREADY_WORN, this );
        return false;
    }
    if ( it->IsWielded() ) {
        Bank().PlayerUI( PLAYERUI_FAIL_WIELDING, this );
        return false;
    }
    ItemList wornList;
    GetWornItems( t, wornList );
    if ( wornList.size() > 1 && t == BIItemType::WEARABLE_RING ) {
        Bank().PlayerUI( PLAYERUI_FAIL_RING, this );
        return false;
    }
    if ( !wornList.empty() && t == BIItemType::WEARABLE_BODY ) {
        Bank().PlayerUI( PLAYERUI_FAIL_BODY, this );
        return false;
    }
    if ( !wornList.empty() && t == BIItemType::WEARABLE_FEET ) {
        Bank().PlayerUI( PLAYERUI_FAIL_FEET, this );
        return false;
    }
    if ( !wornList.empty() && t == BIItemType::WEARABLE_HEAD ) {
        Bank().PlayerUI( PLAYERUI_FAIL_HEAD, this );
        return false;
    }
    if ( !wornList.empty() && t == BIItemType::WEARABLE_HAND ) {
        Bank().PlayerUI( PLAYERUI_FAIL_HAND, this );
        return false;
    }
    if ( !wornList.empty() && t == BIItemType::WEARABLE_NECK ) {
        Bank().PlayerUI( PLAYERUI_FAIL_NECK, this );
        return false;
    }
    it->Wear( true );
    Bank().Manipulation( MANIPULATION_WEAR, this, it );
    *worn = true;
    return true;
}

bool Creature::OnWield( Item** item, bool* wielded )
{
    Item* it = *item;
    uint type;
    it->Call< BIItemType >( &type );
    uint t = type & BIItemType::WIELDABLE;
    if ( !IsItemInInventory( it ) )
        return false;
    if ( !t ) {
        Bank().PlayerUI( PLAYERUI_FAIL_UNWIELDABLE, this );
        return false;
    }
    if ( it->IsWielded() ) {
        Bank().PlayerUI( PLAYERUI_FAIL_ALREADY_WIELDED, this );
        return false;
    }

    if ( it->IsWorn() ) {
        Bank().PlayerUI( PLAYERUI_FAIL_WEARING, this );
        return false;
    }
    ItemList wieldedList;
    GetWieldedItems( BIItemType::WIELDABLE, wieldedList );
    int n = 0;
    foreach ( Item* i, wieldedList ) {
        uint type;
        i->Call< BIItemType >( &type );
        n += type & BIItemType::WIELDABLE_1H ? 1 : 2;
    }
    if ( t == BIItemType::WIELDABLE_1H && n > 1 ) {
        Bank().PlayerUI( PLAYERUI_FAIL_NEED_FREE_HAND, this );
        return false;
    }
    if ( t == BIItemType::WIELDABLE_2H && n > 0 ) {
        Bank().PlayerUI( PLAYERUI_FAIL_NEED_BOTH_HANDS, this );
        return false;
    }
    it->Wield( true );
    if ( t == BIItemType::WIELDABLE_2H )
        Bank().Manipulation( MANIPULATION_WIELD_BOTH, this, it );
    else if ( n == 0 )
        Bank().Manipulation( MANIPULATION_WIELD_MAIN, this, it );
    else
        Bank().Manipulation( MANIPULATION_WIELD_FREE, this, it );
    *wielded = true;
    return true;
}

bool Creature::OnRemove( Item** item, bool* removed )
{
    Item* it = *item;
    if ( !IsItemInInventory( it ) )
        return false;

    if ( it->IsWorn() ) {
        it->Wear( false );
        Bank().Manipulation( MANIPULATION_UNWEAR, this, it );
        *removed = true;
        return true;
    }
    if ( it->IsWielded() ) {
        it->Wield( false );
        Bank().Manipulation( MANIPULATION_UNWIELD, this, it );
        *removed = true;
        return true;
    }

    Bank().PlayerUI( PLAYERUI_FAIL_NOT_EQUIPPED, this );
    return false;
}

bool Creature::OnEat( Item** item, bool* eaten )
{
    Item* it = *item;
    if ( !IsItemInInventory( it ) )
        return false;
    if ( it->IsWielded() ) {
        Bank().PlayerUI( PLAYERUI_FAIL_WIELDING, this );
        return false;
    }
    if ( it->IsWorn() ) {
        Bank().PlayerUI( PLAYERUI_FAIL_WEARING, this );
        return false;
    }
    it->Call< BIOnEaten >( this );
    *eaten = true;
    return true;
}

bool Creature::OnTick()
{
    int hpMax, mpMax, weightMax, ignore;
    double hpRegen, mpRegen;
    Call< BIStats >( &hpMax, &mpMax, &ignore, &ignore, &ignore, &ignore, &ignore, &ignore );
    Call< BIDerivedStats >( &hpRegen, &mpRegen, &weightMax );
    _hpRegen += hpRegen;
    _mpRegen += mpRegen;
    while ( _hpRegen >= 1.0 ) {
        _hpRegen -= 1.0;
        if ( _hp < hpMax )
            _hp++;
    }
    while ( _mpRegen >= 1.0 ) {
        _mpRegen -= 1.0;
        if ( _mp < mpMax )
            _mp++;
    }

    ItemList t;
    GetItemsInInventory( t );
    foreach ( Item* it, t )
        it->Call< BIOnTick >();
    return true;
}
